]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
loginDialog: Add a11y menu
authorFlorian Müllner <fmuellner@gnome.org>
Sat, 7 Jun 2025 17:13:59 +0000 (19:13 +0200)
committerMarge Bot <marge-bot@gnome.org>
Fri, 27 Jun 2025 12:58:43 +0000 (12:58 +0000)
When quick settings become configurable, users can add the a11y
toggles they need to the regular quick settings menu. This does
not really work for the login screen, where all a11y features
should be available by default.

Address this by adding a dedicated a11y menu button next to the
session menu button.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3744>

data/theme/gnome-shell-sass/widgets/_login-lock.scss
js/gdm/loginDialog.js

index 879b3530facca40505458b76277dd73292ae0721..64e295568539edc8a8da0456e5af5131c4ef76ae 100644 (file)
@@ -29,6 +29,7 @@ $_gdm_dialog_width: 25em;
 // buttons on login screen
 .login-dialog-button {
 
+  &.a11y-button,
   &.cancel-button,
   &.switch-user-button,
   &.login-dialog-session-list-button {
index 15ba43a351e666c5eeb04903ffbf5500091c02b2..fc1b5b78c0a0dc6978bc4308e461b4450d38f933 100644 (file)
@@ -40,12 +40,16 @@ import * as ModalDialog from '../ui/modalDialog.js';
 import * as PopupMenu from '../ui/popupMenu.js';
 import * as Realmd from './realmd.js';
 import * as UserWidget from '../ui/userWidget.js';
+import {QuickSettingsMenu} from '../ui/quickSettings.js';
+import * as A11y from '../ui/status/accessibility.js';
 
 const _FADE_ANIMATION_TIME = 250;
 const _SCROLL_ANIMATION_TIME = 500;
 const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
 const _CONFLICTING_SESSION_DIALOG_TIMEOUT = 60;
 
+const N_A11Y_MENU_COLUMNS = 2;
+
 Gio._promisify(Gio.File.prototype, 'load_contents_async');
 
 export const UserListItem = GObject.registerClass({
@@ -410,6 +414,51 @@ const SessionMenuButton = GObject.registerClass({
     }
 });
 
+const A11yMenuButton = GObject.registerClass(
+class A11yMenuButton extends St.Button {
+    constructor() {
+        super({
+            style_class: 'login-dialog-button a11y-button',
+            icon_name: 'org.gnome.Settings-accessibility-symbolic',
+            accessible_name: _('Accessibility'),
+            accessible_role: Atk.Role.MENU,
+            can_focus: true,
+        });
+
+        this._menu = new QuickSettingsMenu(this, N_A11Y_MENU_COLUMNS);
+
+        this._menu.addItem(new A11y.HighContrastToggle());
+        this._menu.addItem(new A11y.MagnifierToggle());
+        this._menu.addItem(new A11y.LargeTextToggle());
+        this._menu.addItem(new A11y.ScreenReaderToggle());
+        this._menu.addItem(new A11y.ScreenKeyboardToggle());
+        this._menu.addItem(new A11y.VisualBellToggle());
+        this._menu.addItem(new A11y.StickyKeysToggle());
+        this._menu.addItem(new A11y.SlowKeysToggle());
+        this._menu.addItem(new A11y.BounceKeysToggle());
+        this._menu.addItem(new A11y.MouseKeysToggle());
+
+        Main.uiGroup.add_child(this._menu.actor);
+        this._menu.actor.hide();
+
+        this._menu.connect('open-state-changed', (menu, isOpen) => {
+            if (isOpen)
+                this.add_style_pseudo_class('active');
+            else
+                this.remove_style_pseudo_class('active');
+        });
+        this._menu.actor.connect('key-press-event',
+            (o, ev) => global.focus_manager.navigate_from_event(ev));
+
+        this._manager = new PopupMenu.PopupMenuManager(this,
+            {actionMode: Shell.ActionMode.NONE});
+        this._manager.addMenu(this._menu);
+
+        this.connect('clicked', () => this._menu.toggle());
+        this.connect('destroy', () => this._menu.destroy());
+    }
+});
+
 export const ConflictingSessionDialog = GObject.registerClass({
     Signals: {
         'cancel': {},
@@ -601,6 +650,9 @@ export const LoginDialog = GObject.registerClass({
         this._sessionMenuButton.show();
         this.add_child(this._sessionMenuButton);
 
+        this._a11yMenuButton = new A11yMenuButton();
+        this.add_child(this._a11yMenuButton);
+
         this._logoBin = new St.Widget({
             style_class: 'login-dialog-logo-bin',
             x_align: Clutter.ActorAlign.CENTER,
@@ -660,10 +712,10 @@ export const LoginDialog = GObject.registerClass({
         return actorBox;
     }
 
-    _getSessionMenuButtonAllocation(dialogBox) {
+    _getA11yMenuButtonAllocation(dialogBox) {
         let actorBox = new Clutter.ActorBox();
 
-        let [, , natWidth, natHeight] = this._sessionMenuButton.get_preferred_size();
+        let [, , natWidth, natHeight] = this._a11yMenuButton.get_preferred_size();
 
         if (this.get_text_direction() === Clutter.TextDirection.RTL)
             actorBox.x1 = dialogBox.x1 + natWidth;
@@ -677,6 +729,26 @@ export const LoginDialog = GObject.registerClass({
         return actorBox;
     }
 
+    _getSessionMenuButtonAllocation(dialogBox, a11yButtonWidth) {
+        const actorBox = new Clutter.ActorBox();
+
+        // Use half the button width as spacing
+        const offset = a11yButtonWidth + Math.round(a11yButtonWidth / 2);
+
+        let [, , natWidth, natHeight] = this._a11yMenuButton.get_preferred_size();
+
+        if (this.get_text_direction() === Clutter.TextDirection.RTL)
+            actorBox.x1 = dialogBox.x1 + offset + natWidth;
+        else
+            actorBox.x1 = dialogBox.x2 - offset - (natWidth * 2);
+
+        actorBox.y1 = dialogBox.y2 - (natHeight * 2);
+        actorBox.x2 = actorBox.x1 + natWidth;
+        actorBox.y2 = actorBox.y1 + natHeight;
+
+        return actorBox;
+    }
+
     _getCenterActorAllocation(dialogBox, actor) {
         let actorBox = new Clutter.ActorBox();
 
@@ -733,9 +805,16 @@ export const LoginDialog = GObject.registerClass({
             logoHeight = logoAllocation.y2 - logoAllocation.y1;
         }
 
+        let a11yMenuButtonAllocation = null;
+        let a11yButtonWidth = 0;
+        if (this._a11yMenuButton.visible) {
+            a11yMenuButtonAllocation = this._getA11yMenuButtonAllocation(dialogBox);
+            a11yButtonWidth = a11yMenuButtonAllocation.get_width();
+        }
+
         let sessionMenuButtonAllocation = null;
         if (this._sessionMenuButton.visible)
-            sessionMenuButtonAllocation = this._getSessionMenuButtonAllocation(dialogBox);
+            sessionMenuButtonAllocation = this._getSessionMenuButtonAllocation(dialogBox, a11yButtonWidth);
 
         // Then figure out if we're overly constrained and need to
         // try a different layout, or if we have what extra space we
@@ -836,6 +915,9 @@ export const LoginDialog = GObject.registerClass({
         if (logoAllocation)
             this._logoBin.allocate(logoAllocation);
 
+        if (a11yMenuButtonAllocation)
+            this._a11yMenuButton.allocate(a11yMenuButtonAllocation);
+
         if (sessionMenuButtonAllocation)
             this._sessionMenuButton.allocate(sessionMenuButtonAllocation);
     }